/**
 * 导出测试用例，YML格式数据封装
 *
 * Created by nongzhizhong on 2020/06/08.
 */

const dot = require('dot');
dot.templateSettings.strip = false;
const {
  VARIABLE_KIND,
  VARIABLE_TYPE,
} = require('smart-rocket-unit');
const tests = require('../../../tests');
const logger = require('../../../logger');
const YmlTestCaseTransformer = require('./yml-test-case-transformer');

const testcaseErrorKind = {
  101: 'A runtime error occurred. Check for issues like division by zero or array out-of-bounds, or review the test case/code.',
  102: 'Timeout: Check for infinite loops in the code.',
  111: 'system error.',
  112: 'An instrumentation error occurred.',
  113: 'A compilation error occurred. Please check the test case data based on the specific error details.',
};
/**
  * 收集变量信息
  * @param area {String} 变量所在数据域
  * @param scope {String} 变量输入输出类型
  * @param variable {Object} 变量
  * @param testCase {Object} 测试用例
  * @param values {String} 变量值
  * @param stubsUsed {Object} 使用过的stub
  * @param index {Object} 测试用例索引
  * @param path {String} 路径
  */
function collectVariableName(area, scope, variable, testCase, values, stubsUsed, index, path) {
  let currentPath;
  if (variable.kind === VARIABLE_KIND.return) {
    currentPath = path;
  } else if (variable.staticsBasePath) {
    currentPath = path === '' ? variable.staticsBasePath : [path, variable.name].join('.');
  } else {
    currentPath = path === '' ? variable.name : [path, variable.name].join('.');
  }
  let findValue = false;
  switch (variable.type) {
    case VARIABLE_TYPE.pointer:
    case VARIABLE_TYPE.basic:
    case VARIABLE_TYPE.enumeration:
      findValue = true;
      break;
    case VARIABLE_TYPE.functionPointer:
      if (stubsUsed && variable.data['@value']) {
        stubsUsed.add(variable.data['@value']);
      }
      findValue = true;
      break;
    case VARIABLE_TYPE.record:
      variable.children.forEach((child) => {
        collectVariableName(area, scope, child, testCase, values, stubsUsed, index, currentPath);
      });
      break;
    case VARIABLE_TYPE.array:
      variable.children.forEach((child, childIndex) => {
        if (variable.staticsBasePath) {
          child.name = `${variable.staticsBasePath}[${childIndex}]`;
        }
        collectVariableName(area, scope, child, testCase, values, stubsUsed, index, path);
      });
      break;
    default:
      break;
  }
  if (findValue) {
    const variables = values[scope][area];
    if (!variables[currentPath]) {
      variables[currentPath] = { data: [], count: 0 };
    }
    while (variables[currentPath].count !== index) {
      variables[currentPath].count += 1;
      variables[currentPath].data.push('0');
    }
    variables[currentPath].count += 1;
    if (variable.type === VARIABLE_TYPE.functionPointer && variable.data['@value']) {
      variables[currentPath].data.push(variable.data['@value']);
    } else if (variable.type === VARIABLE_TYPE.pointer && variable.data['@value']) {
      if (variable.data['@value'] === '0') {
        variables[currentPath].data.push(variable.data['@value']);
      } else if (variable.data['@value'].indexOf('&') !== -1) {
        variables[currentPath].data.push(variable.data['@value'].split('&')[1]);
      }
    } else {
      let val = variable.data['@value'] || '*none*';
      if (val.startsWith('0x')) {
        val = '*none*';
      }
      variables[currentPath].data.push(val);
    }
  }
}

/**
  * 收集桩函数数据
  * @param variable {Object} 变量
  * @param result {Object} 存放收集数据
  * @param path {String} 路径
  * @param currentPath {String} 当前路径
  * @param func {Function} 函数，收集桩函数某个域数据
  */
function collectStubData(variable, result, path, currentPath, func, testCase) {
  switch (variable.type) {
    case VARIABLE_TYPE.record:
      variable.children.forEach((child) => {
        func(child, result, testCase, currentPath);
      });
      break;
    case VARIABLE_TYPE.array:
      variable.children.forEach((child) => {
        func(child, result, testCase, path);
      });
      break;
    case VARIABLE_TYPE.pointer:
      if (variable.data && variable.data['@value']) {
        if (variable.data['@value'] !== '0') {
          if (variable.data['@value'].includes('@')) {
            const [, value] = variable.data['@value'].split('@');
            result[currentPath] = value;
          } else {
            result[currentPath] = variable.data['@value'];
          }
        } else {
          result[currentPath] = variable.data['@value'];
        }
      }
      break;
    case VARIABLE_TYPE.enumeration:
    case VARIABLE_TYPE.functionPointer:
    case VARIABLE_TYPE.basic:
      if (variable.data && variable.data['@value']) {
        result[currentPath] = variable.data['@value'];
      }
      break;
    default:
      break;
  }
}

/**
  * 收集桩函数参数数值
  * @param variable {Object} 变量
  * @param result {Object} 存放收集数据
  * @param path {String} 变量路径
  */
function collectStubParams(variable, result, testCase, path = '') {
  const currentPath = path === '' ? variable.name : [path, variable.name].join('.');
  collectStubData(variable, result, path, currentPath, collectStubParams, testCase);
}

/**
  * 收集桩函数返回值数值
  * @param variable {Object} 变量
  * @param result {Object} 存放收集数据
  * @param path {String} 变量路径
  */
function collectStubReturn(variable, result, testCase, path = '') {
  let currentPath;
  if (variable.kind === VARIABLE_KIND.stubReturn) {
    currentPath = '_ret';
  } else {
    currentPath = [path, variable.name].join('.');
  }
  collectStubData(variable, result, path, currentPath, collectStubReturn, testCase);
}

/**
  * 收集桩函数指针目标的数值
  * @param variable {Object} 变量
  * @param result {Object} 存放收集数据
  * @param path {String} 变量路径
  */
function collectStubPointer(variable, result, testCase, path = '') {
  const currentPath = path === '' ? variable.name : [path, variable.name].join('.');
  collectStubData(variable, result, path, currentPath, collectStubPointer, testCase);
}

/**
  * 收集桩函数子数据
  * @param stubs {Object} 桩函数
  * @param stubName {String} 桩函数名
  * @param field {String} 桩函数域
  * @param sign {String} 函数签名
  */
function collectStubsChildren(stubs, stubName, field, functionYml, testCase) {
  const pointTargets = {};
  const testCaseTimes = [];
  stubs.children.forEach((stubChildren) => {
    const oneTime = {};
    stubChildren.forEach((stub) => {
      if (stub.kind === VARIABLE_KIND.stubParam
        && stub.type === VARIABLE_TYPE.pointer
        && stub.data['@value']
        && stub.data['@value'] !== '0'
        && stub.data['@value'].indexOf('@') !== -1) {
        // 桩函数下的指针目标赋值要把@去掉
        pointTargets[stub.data['@value'].replace('@', '')] = stub.name;
      }
      switch (stub.kind) {
        case VARIABLE_KIND.stubReturn:
          collectStubReturn(stub, oneTime, testCase);
          break;
        case VARIABLE_KIND.stubPointerTarget:
          collectStubPointer(stub, oneTime, testCase);
          break;
        case VARIABLE_KIND.stubParam:
          collectStubParams(stub, oneTime, testCase);
          break;
        default:
          break;
      }
      Object.keys(oneTime).forEach((variableName) => {
        let name = variableName.split('.')[0];
        if (name.endsWith(']')) {
          [name] = variableName.split('[');
        }
        if (pointTargets[name]) {
          const newName = variableName.replace(name, pointTargets[name]);
          oneTime[newName] = oneTime[variableName];
          delete oneTime[variableName];
        }
      });
      Object.keys(oneTime).filter((name) => oneTime[name]).forEach((variableName) => {
        let value = oneTime[variableName].split('.')[0];
        if (value.endsWith(']')) {
          [value] = value.split('[');
        }
        if (value && pointTargets[value]) {
          oneTime[variableName] = oneTime[variableName].replace(value, pointTargets[value]);
        }
      });
    });
    const timeObjects = Object.keys(oneTime);
    timeObjects.forEach((item) => {
      if (item === oneTime[item]) {
        delete oneTime[item];
      }
    });
    if (timeObjects.length > 0) {
      testCaseTimes.push(oneTime);
    }
  });

  try {
    const data = { times: testCaseTimes };
    functionYml[field][stubName].tests.push(data);
  } catch (err) {
    logger.error('', err);
  }
}
/**
   * 补全测试用例对应参数值
   * @param {*} functionYmlFomat 格式化后的YML格式用例
   * @param {*} testCaseNum 测试用例个数
   * @returns
   */
function testCaseDataCompletion(functionYmlFomat, testCaseNum) {
  const { input, output } = functionYmlFomat.funcVariableAndValueMessage;
  const collectValues = [];
  const matchExp = /\[(\d+)\](?=[^\[]*$)/;// eslint-disable-line 
  for (let i = 0; i < testCaseNum; i += 1) {
    collectValues.push([]);
  }
  let areas = Object.keys(input);
  areas.forEach((area) => {
    const variableNames = Object.keys(input[area]);
    variableNames.forEach((variableName) => {
      let newVariableName = variableName;
      if (area === 'mallocVariablesData') {
        newVariableName = `&${variableName}`;
      }
      functionYmlFomat.variables.push(newVariableName);
      functionYmlFomat.variableTypes.push('i');
      const values = input[area][variableName].data;
      while (values.length < testCaseNum) {
        values.push('*none*');
        input[area][variableName].count += 1;
      }
      values.forEach((val, index) => {
        if (val.match(matchExp)) {
          val = val.replace(val.match(matchExp)[0], '');
        }
        collectValues[index].push(val);
      });
    });
  });
  areas = Object.keys(output);
  areas.forEach((area) => {
    const variableNames = Object.keys(output[area]);
    variableNames.forEach((variableName) => {
      let newVariableName = variableName;
      if (area === 'mallocVariablesData') {
        newVariableName = `&${variableName}`;
      }
      functionYmlFomat.variables.push(newVariableName);
      functionYmlFomat.variableTypes.push('o');
      output[area][variableName].data.forEach((val, index) => {
        if (val === '?') {
          val = '*none*';
        }
        if (val.match(matchExp)) {
          val = val.replace(val.match(matchExp)[0], '');
        }
        collectValues[index].push(val);
      });
    });
  });
  const variableTypesCount = functionYmlFomat.variableTypes.length;
  functionYmlFomat.values = collectValues.map((values) => {
    const len = variableTypesCount - values.length;
    for (let i = 0; i < len; i += 1) {
      values.push('*none*');
    }
    return values;
  });
  return functionYmlFomat;
}
/**
 * 处理测试用例中的指针目标问题，
 * 将使用到该指针目标的测试用例排在最前面
 * @param {Object} data yml格式化之后的测试用例数据
 * 注意指针目标多空格情况如下：
 * int malloc[1];int malloc[1][2];int malloc[1]_b_a[12]; int malloc;int s malloc; int  s y malloc;
 * 数据结构
 *
{
  "testModule": "all.c",
  "testobject": "test2",
  "version": "4.0.18",
  "stubs": {
      "stub1": {
          "returnVariableName": "_ret",
          "stubCodeKind": false,
          "returnType": "int",
          "tests": [
              {
                  "times": [
                      {
                          "_ret": "1"
                      }
                  ]
              },
              {
                  "times": [
                      {
                          "_ret": "0",
                          "a[0]": "1"
                      }
                  ]
              },
              {
                  "times": [
                      {
                          "_ret": "0",
                          "a[0]": "2"
                      }
                  ]
              }
          ]
      }
  },
  "variables": [
      "&target_p_0[0]",
      "p",
      "test2()"
  ],
  "variableTypes": [
      "i",
      "i",
      "o"
  ],
  "values": [
      [
          "*none*",
          "*none*",
          "*none*"
      ],
      [
          "0",
          "target_p_0",
          "*none*"
      ],
      [
          "0",
          "target_p_0",
          "*none*"
      ]
  ],
  "mallocDeclaration": [
      "int target_p_0[1]"
  ],
  "testExecSuggestions": [

  ]
}
 */
function handleTestCasesMalloc(data) {
  if (data.mallocDeclaration.length === 0 || data.values.length === 0) {
    return data;
  }
  let addInitMallocTestCase = false;
  const initMallocTestCase = data.variables.map(() => '*none*');
  const regExpr = /\s([^[]+)(?:\[|$)/;
  const collectMallocValueName = [];
  data.mallocDeclaration.forEach((mallocName) => {
    if (!mallocName.match(regExpr)) {
      return;
    }
    [, mallocName] = mallocName.match(regExpr);
    if (mallocName.indexOf(' ') !== -1) {
      mallocName = (mallocName.split(' ')).pop();
    }
    collectMallocValueName.push(mallocName);
  });
  // 构建初始化指针目标的用例
  for (let i = 0; i < data.values.length; i += 1) {
    for (let j = 0; j < data.values[i].length; j += 1) {
      const mallocVal = data.values[i][j];
      if (collectMallocValueName.includes(mallocVal)) {
        addInitMallocTestCase = true;
        initMallocTestCase[j] = mallocVal;
      }
    }
  }
  if (!addInitMallocTestCase) {
    return data;
  }
  // 复制桩函数信息
  if (data.stubs) {
    Object.keys(data.stubs).forEach((stubName) => {
      if (!data.stubs[stubName].tests || data.stubs[stubName].tests.length === 0) {
        return;
      }
      data.stubs[stubName].tests.unshift(data.stubs[stubName].tests[0]);
    });
  }
  data.values.unshift(initMallocTestCase);
  return data;
}
/**
 * 构建yml数据结构
 * @param {*} testCases  测试用例
 * @param {*} functionVariable  函数变量
 * @param {*} typeSystem 类型系统
 * @param {*} functionName 函数名
 * @param {*} functionYml YML导出结构
 * @param {*} locale 国际化
 * @returns
 *
 * 严格保持tessy中测试用例yml结构变量类型顺序
 * 全局变量（包含局部静态变量）
 * 指针目标
 * 形参
 * 函数返回值
 */
function ymlDataHandle(
  testCases,
  functionVariable,
  typeSystem,
  functionName,
  functionYml,
  locale,
) {
  testCases.forEach((originalTestcase, index) => {
    const testCase = new YmlTestCaseTransformer(
      tests.getJson(originalTestcase, locale),
      functionVariable,
      typeSystem,
    );
    testCase.transform();
    const variableStubs = functionVariable.stubs || {};
    testCase.id = testCase.testCaseData.testId;
    // 被函数指针变量使用到的手动添加的桩函数
    const stubsUsed = new Set();
    // 处理全局变量，形参
    ['globalVariablesData', 'globalOutputData'].forEach((area) => {
      const scope = area === 'globalOutputData' ? 'output' : 'input';
      testCase[area].forEach((variable) => {
        collectVariableName(area, scope, variable, testCase, functionYml.funcVariableAndValueMessage, stubsUsed, index, '');
      });
    });
    // 处理静态局部变量
    ['staticsData', 'staticsOutputData'].forEach((area) => {
      const scope = area === 'staticsData' ? 'input' : 'output';
      testCase[area].forEach((variable) => {
        variable.staticsBasePath = `${functionYml.testobject}::${variable.name}#${parseInt(variable.index, 10) + 1}`;
        collectVariableName(area, scope, variable, testCase, functionYml.funcVariableAndValueMessage, stubsUsed, index, '');
      });
    });
    // 处理指针目标
    ['mallocVariablesData', 'mallocOutputData'].forEach((area) => {
      const scope = area === 'mallocOutputData' ? 'output' : 'input';
      testCase[area].forEach((variable) => {
        let declaration;
        if (variable.type === VARIABLE_TYPE.array) {
          declaration = `${variable.elementTypeName} ${variable.name}[${variable.length}]`;
        } else {
          declaration = `${variable.typeName} ${variable.name}`;
        }
        if (!functionYml.mallocDeclaration.includes(declaration)) {
          functionYml.mallocDeclaration.push(declaration);
        }
        collectVariableName(area, scope, variable, testCase, functionYml.funcVariableAndValueMessage, stubsUsed, index, '');
      });
    });
    // 处理形参
    testCase.paramVariablesData.forEach((variable) => {
      collectVariableName('paramVariablesData', 'input', variable, testCase, functionYml.funcVariableAndValueMessage, stubsUsed, index, '');
    });
    // 处理返回值
    testCase.returnValueData.forEach((variable) => {
      collectVariableName('returnValueData', 'output', variable, testCase, functionYml.funcVariableAndValueMessage, stubsUsed, index, functionName);
    });
    testCase.stubsData.forEach((stub) => {
      const returnType = stub.typeName === 'void' ? '' : stub.typeName;
      const returnVariableName = stub.typeName === 'void' ? '' : '_ret';
      const { stubName, currentStubKind } = stub;
      const variableStub = variableStubs[stubName];
      const stubData = {
        returnVariableName,
        stubCodeKind: currentStubKind === tests.stubKind.code,
        returnType,
        tests: [],
      };
      const stubTypeName = variableStub.stubsTriggerKind && variableStub.stubsTriggerKind === tests.stubTriggerKind.manual ? 'manualStubs' : 'stubs';
      // 处理手动添加的桩函数
      if (variableStub.stubsTriggerKind === tests.stubTriggerKind.manual && !functionYml.manualStubs[stubName]) {
        functionYml.manualStubs[stubName] = { ...stubData, fullName: `${stub.typeName} ${stub.name}` };
      } else if (!functionYml.stubs[stubName]) {
        functionYml.stubs[stubName] = { ...stubData };
      }
      // 代码打桩
      if (stubData.stubCodeKind) {
        functionYml.stubs[stubName].body = originalTestcase.data.stubs[stubName].body || '';
      }
      collectStubsChildren(stub, stubName, stubTypeName, functionYml, testCase, index);
    });
    // 处理执行错误用例的指导建议
    if (originalTestcase.resultKind && testcaseErrorKind[originalTestcase.resultKind]) {
      functionYml.testExecSuggestions.push({
        index: `'${index + 1}'`,
        suggesttions: testcaseErrorKind[originalTestcase.resultKind],
      });
    }
  });
  return handleTestCasesMalloc(testCaseDataCompletion(functionYml, testCases.length));
}

module.exports = {
  ymlDataHandle,
};
